package com.cci.zkweb.core.services.impl;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;

import com.cci.lib.ext.config.SharedConfig;
import com.cci.zkweb.core.entity.p8.ClassProperty;
import com.cci.zkweb.core.entity.p8.Document;
import com.cci.zkweb.core.entity.p8.Folder;
import com.cci.zkweb.core.entity.p8.P8Object;
import com.cci.zkweb.core.services.P8Service;
import com.filenet.api.admin.ClassDefinition;
import com.filenet.api.admin.PropertyDefinition;
import com.filenet.api.collection.DocumentSet;
import com.filenet.api.collection.FolderSet;
import com.filenet.api.collection.PropertyDefinitionList;
import com.filenet.api.core.Connection;
import com.filenet.api.core.Domain;
import com.filenet.api.core.Factory;
import com.filenet.api.core.ObjectStore;
import com.filenet.api.util.UserContext;

public class P8ServiceImpl 
implements P8Service {
	private Connection connection;
	private ObjectStore os;
	private Subject subject;

	public P8ServiceImpl() {
		this.connection = Factory.Connection.getConnection(SharedConfig.getInstance().getValue("p8", "P8_EJB_URL"));
	}

	public Connection getConnection() {
		return this.connection;
	}

	public void LoginToServiceContext(Subject subject) {
		this.subject = subject;
		try {
			applyUserContext();

			Domain domain = Factory.Domain.fetchInstance(
					this.connection, 
					SharedConfig.getInstance().getValue("p8", "P8_DOMAIN"), 
					null);
			this.os = Factory.ObjectStore.fetchInstance(domain, "SISTEMI", null);
		} finally {
			removeUserContext();
		}
	}

	/**
	 * Apply security to execution
	 */
	private void applyUserContext() {
		UserContext.get().pushSubject(this.subject);
	}

	/**
	 * Remove security from execution
	 */
	private void removeUserContext() {
		UserContext.get().popSubject();
	}

	/**
	 * Get the root folder
	 */
	@Override
	public Folder getRootFolder() {
		Folder _f = null;
		try {
			applyUserContext();

			com.filenet.api.core.Folder fld = Factory.Folder.fetchInstance(this.os, "/", null);
			_f = getExtendedFolderInfo(fld);
			_f.setParent(null);
			_f.setName(this.os.get_Name());
		} finally {
			removeUserContext();
		}
		return _f;
	}

	/**
	 * Get subfolders of folder
	 */
	@Override
	public List<Folder> getSubFolders(Folder folder) {
		List<Folder> subFolders = new ArrayList<Folder>();
		try {
			applyUserContext();

			com.filenet.api.core.Folder fld = Factory.Folder.fetchInstance(os, folder.getPath(), null);
			if(fld != null) {
				FolderSet fs = fld.get_SubFolders();
				if(fs != null) {
					@SuppressWarnings("unchecked")
					Iterator<com.filenet.api.core.Folder> iter = fs.iterator();
					while(iter.hasNext()) {
						com.filenet.api.core.Folder currF = iter.next();
						Folder _f = getExtendedFolderInfo(currF);
						_f.setParent(folder);
						subFolders.add(_f);
					}
				}
			}
		} finally {
			removeUserContext();
		}
		return subFolders;
	}

	/**
	 * Get documents in folder
	 */
	@Override
	public List<Document> getDocumentsInFolder(Folder folder) {
		List<Document> documents = new ArrayList<Document>();

		try {
			applyUserContext();

			com.filenet.api.core.Folder fld = Factory.Folder.fetchInstance(os, folder.getPath(), null);
			if(fld != null) {
				DocumentSet ds = fld.get_ContainedDocuments();
				if(ds != null) {
					@SuppressWarnings("unchecked")
					Iterator<com.filenet.api.core.Document> iter = ds.iterator();
					while(iter.hasNext()) {
						com.filenet.api.core.Document currD = iter.next();
						documents.add(getExtendedDocumentInfo(currD));
					}
				}
			}
		} finally {
			removeUserContext();
		}
		folder.setDocList(documents);
		return documents;
	}
	
	/**
	 * Get all information related to the document
	 * @param fld
	 * @return
	 */
	private Folder getExtendedFolderInfo(com.filenet.api.core.Folder fld) {
		Folder _f = new Folder();
		_f.setName(fld.get_Name());
		_f.setAuthor(fld.get_Creator());
		_f.setId(fld.get_Id().toString());
		_f.setClassDoc(fld.getClassName());
		_f.setLastEditor(fld.get_LastModifier());
		_f.setPath(fld.get_PathName());

		return _f;
	}

	/**
	 * Get all information related to the document
	 * @param doc
	 * @return
	 */
	private Document getExtendedDocumentInfo(com.filenet.api.core.Document doc) {
		Document _d = new Document();
		_d.setName(doc.get_Name());
		_d.setAuthor(doc.get_Creator());
		_d.setMimeType(doc.get_MimeType());
		_d.setId(doc.get_Id().toString());
		_d.setClassDoc(doc.getClassName());
		_d.setMajor(doc.get_MajorVersionNumber().toString());
		_d.setMinor(doc.get_MinorVersionNumber().toString());
		_d.setLastEditor(doc.get_LastModifier());
		_d.setDimension(doc.get_ContentSize());

		return _d;
	}
	
	/**
	 * Read info from generic obj
	 * @param obj
	 */
	public void readObjectClassInfo(P8Object obj) {
		if(obj.getSystemClassProperties() == null) {
			if(obj.isFolder())
				readFolderClassInfo((Folder)obj);
			else
				readDocumentClassInfo((Document)obj);
		}
	}

	/**
	 * Read document class info from doc
	 * @param doc
	 */
	public void readDocumentClassInfo(Document doc) {
		try {
			applyUserContext();
			
			com.filenet.api.core.Document fdoc = Factory.Document.fetchInstance(this.os, doc.getId(), null);
			if(fdoc != null) {
				doc.setCustomClassProperties(getClassInfo(fdoc, false));
				doc.setSystemClassProperties(getClassInfo(fdoc, true));
			}
		} finally {
			removeUserContext();
		}
	}
	
	/**
	 * Read document class info from doc
	 * @param doc
	 */
	public void readFolderClassInfo(Folder fld) {
		try {
			applyUserContext();
			
			com.filenet.api.core.Folder f = Factory.Folder.fetchInstance(this.os, fld.getId(), null);
			if(f != null) {
				fld.setCustomClassProperties(getClassInfo(f, false));
				fld.setSystemClassProperties(getClassInfo(f, true));
			}
		} finally {
			removeUserContext();
		}
	}
	
	/**
	 * Get specific class properties
	 * @param iObj
	 * @return
	 */
	@SuppressWarnings("unused")
	private Map<String, String> getClassInfoMap(com.filenet.api.core.IndependentObject iObj) {
		Map<String, String> propMap = new HashMap<String, String>();
		List<PropertyDefinition> defList = getClassCustomProperties(this.os, iObj.getClassName());
		for(PropertyDefinition pd : defList) {
			Object obj = iObj.getProperties().getObjectValue(pd.get_SymbolicName());
			propMap.put(pd.get_Name(), obj != null ? obj.toString() : "");
		}
		return propMap;
	}
	
	/**
	 * Get class info as ClassProperty Object
	 * @param iObj
	 * @return
	 */
	private List<ClassProperty> getClassInfo(com.filenet.api.core.IndependentObject iObj, boolean system) {
		List<ClassProperty> propMap = new ArrayList<ClassProperty>();
		List<PropertyDefinition> defList;
		if(system) {
			defList = getClassSystemProperties(this.os, iObj.getClassName());
		} else {
			defList = getClassCustomProperties(this.os, iObj.getClassName());
		}
		
		for(PropertyDefinition pd : defList) {
			Object obj = iObj.getProperties().getObjectValue(pd.get_SymbolicName());
			if(obj != null && obj.getClass() != null && obj.getClass().getCanonicalName().contains("com.filenet.apiimpl"))
				continue;
			ClassProperty cp = new ClassProperty();
			cp.setId(pd.get_Id().toString());
			cp.setHidden(false);
			cp.setName(pd.get_Name());
			cp.setSystem(false);
			cp.setValue(obj);
			
			propMap.add(cp);
		}
		
		return propMap;
	}
	
	/**
	 * Get the custom class properties
	 * 
	 * @param objectStore
	 * @param className
	 * @return
	 */
	public static List<PropertyDefinition> getClassCustomProperties (ObjectStore objectStore, String className){
		ClassDefinition objClassDesc = Factory.ClassDefinition.fetchInstance(objectStore, className, null);
		// Get PropertyDescriptions property from the property cache	
		PropertyDefinitionList props = objClassDesc.get_PropertyDefinitions(); 
		Iterator<?> iter = props.iterator();

		List<PropertyDefinition> res = new ArrayList<PropertyDefinition>();

		while(iter.hasNext()) {
			PropertyDefinition pp = (PropertyDefinition)iter.next();

			if(!pp.get_IsSystemOwned() && !pp.get_IsHidden()) {	
				res.add(pp);	
			}
		}	
		return res;	
	}
	
	/**
	 * Get the system class properties
	 * 
	 * @param objectStore
	 * @param className
	 * @return
	 */
	public static List<PropertyDefinition> getClassSystemProperties (ObjectStore objectStore, String className){
		ClassDefinition objClassDesc = Factory.ClassDefinition.fetchInstance(objectStore, className, null);
		// Get PropertyDescriptions property from the property cache	
		PropertyDefinitionList props = objClassDesc.get_PropertyDefinitions(); 
		Iterator<?> iter = props.iterator();

		List<PropertyDefinition> res = new ArrayList<PropertyDefinition>();

		while(iter.hasNext()) {
			PropertyDefinition pp = (PropertyDefinition)iter.next();

			if(pp.get_IsSystemOwned() && !pp.get_IsHidden()) {	
				res.add(pp);	
			}
		}	
		return res;	
	}

	/**
	 * Count subfolders in folder
	 */
	@Override
	public int getSubFoldersCount(Folder folder) {
		int count = 0;

		try {
			applyUserContext();

			com.filenet.api.core.Folder fld = Factory.Folder.fetchInstance(os, folder.getPath(), null);
			if(fld != null) {
				FolderSet fs = fld.get_SubFolders();
				if(fs != null) {
					@SuppressWarnings("unchecked")
					Iterator<com.filenet.api.core.Folder> iter = fs.iterator();
					while(iter.hasNext()) {
						iter.next();
						count++;
					}
				}
			}
		} finally {
			removeUserContext();
		}
		return count;
	}

	/**
	 * Get the document stream
	 */
	@Override
	public InputStream getDocumentStream(Document doc) {
		try {
			applyUserContext();

			com.filenet.api.core.Document fdoc = Factory.Document.fetchInstance(this.os, doc.getId(), null);
			if(fdoc != null) {
				return fdoc.accessContentStream(0);
			}
		} finally {
			removeUserContext();
		}
		return null;
	}
}
